/**
 * @fileoverview
 *
 * utility functions
 * current scope is mainly related to additional operations on objects or arguments
 *
 * TODO combine functions defaults with overrides into new one (updateObject) with some additional param for defining behaviour
 * TODO submit patch with proposal of adding goog.object.update function to Closure Library
 *
 * @author Sebastian Brzuzek sebastian@brzuzek.pl
 */

goog.provide('uxperf.utils');

goog.require('goog.array');
goog.require('goog.json');

/**
 * iterate over each property in object
 *
 * @param {!Object} obj object to iterate
 * @param {!Function} iterator function to execute for each property
 *                    it will have 3 arguments provided:
 *                    - property name
 *                    - property value
 *                    - result of previous execution of function
 * @param {Object=} opt_context optional context of iterator execution (value available in this)
 * @param {*=} opt_init optional initial value passed as third
 *            argument for first iterator execution
 * @return {*}
 */
uxperf.utils.each = function(obj, iterator, opt_context, opt_init) {
  var res = opt_init;
	for (var i in obj) res = iterator.call(opt_context, i, obj[i], res);
	return res;
};

/** @const default recursion limit */
uxperf.utils.DEFAULTS_MAX_RLEVEL = 5;

/**
 * set default values in object
 * supports embeded sub objects to be set to default
 *
 * @param {!Object} result object to set
 * @param {Object} def object with default data
 * @param {number=} opt_maxrlevel maximum recursion level
 *                  set to -1 for no limit (5 by default)
 * @param {number=} opt_rlevel do not set this parameter
 *                  (parameter used internaly for
 *                  recursion level checking)
 * @return {!Object} reference to result object
 */
uxperf.utils.defaults = function (result, def, opt_maxrlevel, opt_rlevel) {
	if (!def) return result;
	opt_rlevel = opt_rlevel || 0;
	opt_maxrlevel = opt_maxrlevel || uxperf.utils.DEFAULTS_MAX_RLEVEL;
	//NDEBUG || console.log('current rlevel = ', opt_rlevel);
	if ((opt_maxrlevel >= 0) && (opt_rlevel >= opt_maxrlevel)) return result;
	opt_rlevel++;
	
	for(var k in def) {
		//NDEBUG || console.log('k=%s, type of def[k]=%s, res[k]=%j', k, typeof def[k], result[k]);
		if (result[k]===undefined) {
			//NDEBUG || console.log('res[k] is undefined so res[k]=def[k]=%j',def[k]);
			result[k] = def[k];
		} else if ( (result[k] && (typeof result[k] === 'object') || (typeof result[k] === 'array'))
								&& ((typeof def[k] === 'object') || (typeof def[k] === 'array')) ) {
			//NDEBUG || console.log('res[k] is not null and is object or array and def[k] is object or array so recurent call is executed');
			result[k] = uxperf.utils.defaults(result[k], def[k], opt_maxrlevel, opt_rlevel);
		}
	}
	return result;
};

/**
 * overrides values in object
 * supports embeded sub objects to be set
 *
 * @param {!Object} result object to set
 * @param {Object} data object with overriding data
 * @param {number=} opt_maxrlevel maximum recursion level
 *                  set to -1 for no limit (5 by default)
 * @param {number=} opt_rlevel do not set this parameter
 *                  (parameter used internaly for
 *                  recursion level checking)
 * @return {!Object} reference to result object
 */
uxperf.utils.overrides = function (result, data, opt_maxrlevel, opt_rlevel) {
	if (!data) return result;
	opt_rlevel = opt_rlevel || 0;
	opt_maxrlevel = opt_maxrlevel || uxperf.utils.DEFAULTS_MAX_RLEVEL;
	//NDEBUG || console.log('current rlevel = ', opt_rlevel);
	if ((opt_maxrlevel >= 0) && (opt_rlevel >= opt_maxrlevel)) return result;
	opt_rlevel++;
	
	for(var k in data) {
		//NDEBUG || console.log('k=%s, type of data[k]=%s, res[k]=%j', k, typeof data[k], result[k]);
		if ( (result[k] && (typeof result[k] === 'object') || (typeof result[k] === 'array'))
								&& ((typeof data[k] === 'object') || (typeof data[k] === 'array')) ) {
			//NDEBUG || console.log('res[k] is not null and is object or array and def[k] is object or array so recurent call is executed');
			result[k] = uxperf.utils.overrides(result[k], data[k], opt_maxrlevel, opt_rlevel);
		} else {
			//NDEBUG || console.log('res[k] is not array or object so set res[k]=data[k]=%j',data[k]);
			result[k] = data[k];			
		}
	}
	return result;
};


/**
 * get dynamic list of arguments for function from given offset
 *
 * usage example:
 * 
 * <code>
 * 
 *   function test1(msg1, opt_msg2) {
 *     alert(msg1);
 *     if (msg2) alert(msg2);
 *   }
 *   function test2(msg1, level, opt_msg) {
 *     alert(level + ': ' + msg1);
 *     if (opt_msg) alert(level + ': ' + opt_msg);
 *   }
 *
 *   function realtest(prm1, var_args) {
 *     switch (prm1) {
 *       case 'test1':
 *         test1.apply(this, uxperf.utils.optionalArgs(1, arguments, true));
 *         break;
 *       case 'test2':
 *         test2.apply(this, uxperf.utils.optionalArgs(1, arguments, true));
 *     }
 *   }
 *
 *   realtest('test1', 'test message');
 *   realtest('test1', 'test message1', 'testmessage2');
 *   realtest('test1', ['test msg 1', 'test msg 2']);
 *   realtest('test2', 'msg', 'INFO');
 *   realtest('test2', 'msg', 'INFO', 'additional msg');
 *   realtest('test2', ['msg', 'INFO', 'additional msg']);
 *   
 * </code>
 * 
 * @param {number} offset of first dynamic argument (zero indexed)
 * @param {Arguments} args original arguments
 * @param {boolean} firstAsArrayOfArgs flag if we should try to use
 *                                     first dynamic argument as complete
 *                                     argument list if there is
 *                                     no more arguments and first one
 *                                     is an array
 *                                     
 * @return {undefined|Array} array of arguments to use in apply function
 */
uxperf.utils.optionalArgs = function (offset, args, firstAsArrayOfArgs) {
	if (args.length < offset + 1) {
		return undefined;
	}
	if (firstAsArrayOfArgs && (args.length == offset + 1)) {
		if (goog.isArray(args[offset]))
			return args[offset];
		return [args[offset]];
	}
	var arr = goog.array.toArray(args);
	for (var i=0; i<offset; i++) arr.shift();
	return arr;
};

/**
 * prepare diagnostic error message from errorInfo
 *
 * @param {*} errorInfo
 * @param {number=} opt_maxrlevel maximum recursion level
 *                  set to -1 for no limit (5 by default)
 * @param {number=} opt_rlevel do not set this parameter
 *                  (parameter used internaly for
 *                  recursion level checking)
 * @return {string}
 */
uxperf.utils.errorInfoToMsg = function(errorInfo, opt_maxrlevel, opt_rlevel) {
	if (goog.isString(errorInfo)) return errorInfo;
	opt_rlevel = opt_rlevel || 0;
	opt_maxrlevel = opt_maxrlevel || uxperf.utils.DEFAULTS_MAX_RLEVEL;
	if ((opt_maxrlevel >= 0) && (opt_rlevel >= opt_maxrlevel)) return 'RECURSION LIMIT';
	opt_rlevel++;

	var res = undefined;
	if (goog.isArray(errorInfo)) {
		var arr = /** @type {Array} */ errorInfo;
		res = ['['];
		for (var i=0; i<arr.length; i++) {
			res.push(uxperf.utils.errorInfoToMsg(arr[i], opt_maxrlevel, opt_rlevel), ',');
		}
		res.push(']');
		return res.join('');
	}
	if (goog.isObject(errorInfo)) {
		var obj = /** @type {Object} */ errorInfo;
		
		if (obj.getMessage)
			return uxperf.utils.errorInfoToMsg(obj.getMessage(), opt_maxrlevel, opt_rlevel);
		if (obj.toString)
			return uxperf.utils.errorInfoToMsg(obj.toString(), opt_maxrlevel, opt_rlevel);
		if (obj.getLastError)
			return uxperf.utils.errorInfoToMsg(obj.getLastError(), opt_maxrlevel, opt_rlevel);
		if (obj.getLastErrorInfo)
			return uxperf.utils.errorInfoToMsg(obj.getLastErrorInfo(), opt_maxrlevel, opt_rlevel);
		
		res = ['{'];
		for (var k in obj) {
			if (obj.hasOwnProperty(k))
				res.push(uxperf.utils.errorInfoToMsg(obj[k], opt_maxrlevel, opt_rlevel), ',');
		}
		res.push('}');
		return res.join('');
	}
	return goog.typeOf(errorInfo);
};